package com.qunar.train.mock.controller;

import com.qunar.train.mock.util.AegisLog;
import com.qunar.train.mock.util.ConfigHandle;

import org.junit.Test;
import org.junit.internal.runners.statements.FailOnTimeout;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.statements.SpringFailOnTimeout;

import java.lang.management.ManagementFactory;
import java.util.List;
import java.util.Map;

/**
 * @author: xudong.tai
 * @since: 2018/6/12 Description:
 */
@SuppressWarnings("deprecation")
public class TrainRunner extends SpringJUnit4ClassRunner {

    private Class<?> clazz;
    private long defaultJunitTimeout = 0L;

    public TrainRunner(Class<?> klass) throws InitializationError {
        super(klass);
        this.clazz = klass;
    }

    // 拦截 BeforeClass 事件
    protected Statement withBeforeClasses(final Statement statement) {
        final Statement junitStatement = super.withBeforeClasses(statement);
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                AegisLog.info("ClassName : " + clazz.getName());
                junitStatement.evaluate();
            }

        };
    }

    // 拦截每一个方法的 Before 事件
    protected Statement withBefores(final FrameworkMethod method, Object target, final Statement statement) {
        AegisLog.info("MethodName : " + method.getName());
        final Statement junitStatement = super.withBefores(method, target, statement);
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                junitStatement.evaluate();
            }
        };
    }

    // 截获每一个测试方法的 after 事件
    protected Statement withAfters(final FrameworkMethod method, Object target, final Statement statement) {
        //reset mock-classes
        //AegisLog.info("reset mock-classes following...");
        TrainMock.clear();
        //AegisLog.info("reset mock-classes end.");

        final Statement junitStatement = super.withAfters(method, target, statement);
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                junitStatement.evaluate();
            }

        };
    }

    // 截获测试类的 after 事件
    protected Statement withAfterClasses(final Statement statement) {
        final Statement junitStatement = super.withAfterClasses(statement);
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                junitStatement.evaluate();
            }
        };
    }

    @Override
    protected long getJUnitTimeout(FrameworkMethod frameworkMethod) {

        if (isDebugMode()) return 0L;

        // 定制junit timeout
        Test test = frameworkMethod.getAnnotation(Test.class);
        if (test != null && test.timeout() > 0) {
            return test.timeout();
        } else {
            Map<String, String> qunarEnv = null;
            try {
                qunarEnv = ConfigHandle.loadConfig("qunar-env.properties");
            } catch (Exception e) {
                AegisLog.error(e);
            }
            long timeout = 0;
            if (qunarEnv.isEmpty() || !qunarEnv.containsKey("junit.timeout")) {
                timeout = defaultJunitTimeout;
            } else {
                timeout = Long.parseLong(qunarEnv.get("junit.timeout"));
                if (timeout < 0) {
                    timeout = defaultJunitTimeout;
                }
            }
            return timeout;
        }
    }

    private Boolean isDebugMode() {
        List<String> args = ManagementFactory.getRuntimeMXBean().getInputArguments();
        boolean isDebug = false;
        for (String arg : args) {
            if (arg.startsWith("-Xrunjdwp") || arg.startsWith("-agentlib:jdwp")) {
                isDebug = true;
                break;
            }
        }
        return isDebug;
    }

    @Override
    protected Statement withPotentialTimeout(FrameworkMethod frameworkMethod, Object testInstance, Statement next) {
        Statement statement = null;
        long springTimeout = getSpringTimeout(frameworkMethod);
        long junitTimeout = getJUnitTimeout(frameworkMethod);
        if (springTimeout > 0 && junitTimeout > 0) {
            String msg = String.format("Test method [%s] has been configured with Spring's @Timed(millis=%s) and " +
                    "JUnit's @Test(timeout=%s) annotations, but only one declaration of a 'timeout' is " +
                    "permitted per test method.", frameworkMethod.getMethod(), springTimeout, junitTimeout);
            AegisLog.error(msg);
            throw new IllegalStateException(msg);
        } else if (springTimeout > 0) {
            statement = new SpringFailOnTimeout(next, springTimeout);
        } else if (junitTimeout > 0) {
            statement = new FailOnTimeout(next, junitTimeout);
        } else {
            statement = next;
        }
        return statement;
    }
}
